
module rom (
    addr,
    dat,
    rw,
    romsel,
);
    input wire[14:0] addr;
    output wire[7:0] dat;
    input wire rw;
    input wire romsel;

    reg [7:0]  rom_dat['h0000:'h7fff];
    wire[7:0]  out_dat = rom_dat[addr];
    wire cs;
    wire isout = cs&rw;
    assign #1 cs = romsel;
    assign dat = isout?out_dat:8'hzz;     //romsel高电平,rw高电平
    initial begin
        $readmemh("mario.hex",rom_dat);
    end
endmodule

module ppu_rom(
    input wire[13:0] addra,
    output wire[7:0] doa,
    input wire clka,
    input wire rsta,
    input wire cs
);
    reg [7:0] mem[0:'h3fff];
    integer i;
    integer x;
    initial begin
        $readmemh("D:\\src\\fpga\\fpxnes\\ppu_mario.hex",mem);
    end
    assign doa = ~cs?dat_reg:8'hzz;
    reg[7:0] dat_reg = 'd0;

    always @(posedge clka) begin
        dat_reg <= mem[addra[13:0]];
    end

endmodule

module sram(
    addr,
    dat,
    rw,
    rd,
    cs,

    iscpu,
);
    input wire[10:0] addr;
    inout wire[7:0] dat;
    input wire rw;
    input wire rd;
    input wire cs;
    input wire iscpu;

    wire sram_cs;
    wire sram_rw;
    assign #1 sram_cs = cs;
    assign #1 sram_rw = rw;

    //reg [7:0] dat_out = 'd0;
    wire [7:0] dat_out = mem[addr];
    wire isout = (~(sram_cs)&rw&~rd);
    assign dat =
    isout?dat_out:8'hzz;
    ;
    reg [7:0] mem[0:'h7ff];
    integer i;
    integer x;
    initial begin
        // for(i=0;i<'h800;i=i+1)begin
        //     mem[i] <= 8'h00;
        // end

        //$readmemh("D:\\src\\fpga\\fpxnes\\ppu_mario.hex",mem);
        if(iscpu)
            $readmemh("D:\\src\\fpga\\fpxnes\\mario_ram.hex",mem);
        else
            $readmemh("D:\\src\\fpga\\fpxnes\\mario_name_ram.hex",mem);
            // for(i=0;i<'h1000;i=i+1)begin
            //     mem[i] <= i[7:0];
            // end
    end

    always @(negedge sram_rw or negedge sram_cs) begin
        if(~sram_rw&~sram_cs)begin
            mem[addr[10:0]] <= dat;
        end
    end

    wire[15:0] test_data = {mem['h1ff],mem['h1fe]};

endmodule



module ppu_test2(
    ppu_addr,
    ppu_dat,
    ppu_rw,
    ppu_rd,
    ppu_clk,
    ppu_nmi,
    dat_out,
    out_pixel,

    cpu_addr,
    cpu_dat,
    cpu_rw,
    ppu_cs,

    x,
    y,
);
    output wire[13:0] ppu_addr;
    output wire ppu_rw;
    output wire ppu_rd;
    output wire[7:0] ppu_dat;
    input wire ppu_clk;
    output wire ppu_nmi;
    output reg[7:0] dat_out;
    output wire[5:0] out_pixel;
    output wire[8:0] x;
    output wire[8:0] y;

    input wire[2:0] cpu_addr;
    inout wire[7:0] cpu_dat;
    input wire cpu_rw;
    input wire ppu_cs;

    reg[15:0]count = 'd0;

    wire[7:0] R,G,B;
    reg [7:0] pal_R['h3f:0];
    reg [7:0] pal_G['h3f:0];
    reg [7:0] pal_B['h3f:0];
    assign R = pal_R[out_pixel];
    assign G = pal_G[out_pixel];
    assign B = pal_B[out_pixel];
    initial begin
        pal_R['h00] <= 'h0C * 8;pal_B['h00] <= 'h0C * 8;pal_G['h00] <= 'h0C * 8;
        pal_R['h01] <= 'h00 * 8;pal_B['h01] <= 'h10 * 8;pal_G['h01] <= 'h04 * 8;
        pal_R['h02] <= 'h00 * 8;pal_B['h02] <= 'h18 * 8;pal_G['h02] <= 'h00 * 8;
        pal_R['h03] <= 'h0C * 8;pal_B['h03] <= 'h18 * 8;pal_G['h03] <= 'h08 * 8;
        pal_R['h04] <= 'h10 * 8;pal_B['h04] <= 'h0C * 8;pal_G['h04] <= 'h00 * 8;
        pal_R['h05] <= 'h14 * 8;pal_B['h05] <= 'h0C * 8;pal_G['h05] <= 'h00 * 8;
        pal_R['h06] <= 'h14 * 8;pal_B['h06] <= 'h00 * 8;pal_G['h06] <= 'h04 * 8;
        pal_R['h07] <= 'h10 * 8;pal_B['h07] <= 'h00 * 8;pal_G['h07] <= 'h08 * 8;
        pal_R['h08] <= 'h0C * 8;pal_B['h08] <= 'h00 * 8;pal_G['h08] <= 'h08 * 8;
        pal_R['h09] <= 'h04 * 8;pal_B['h09] <= 'h00 * 8;pal_G['h09] <= 'h08 * 8;
        pal_R['h0A] <= 'h00 * 8;pal_B['h0A] <= 'h04 * 8;pal_G['h0A] <= 'h0C * 8;
        pal_R['h0B] <= 'h00 * 8;pal_B['h0B] <= 'h00 * 8;pal_G['h0B] <= 'h10 * 8;
        pal_R['h0C] <= 'h00 * 8;pal_B['h0C] <= 'h08 * 8;pal_G['h0C] <= 'h08 * 8;
        pal_R['h0D] <= 'h00 * 8;pal_B['h0D] <= 'h00 * 8;pal_G['h0D] <= 'h00 * 8;
        pal_R['h0E] <= 'h00 * 8;pal_B['h0E] <= 'h00 * 8;pal_G['h0E] <= 'h00 * 8;
        pal_R['h0F] <= 'h00 * 8;pal_B['h0F] <= 'h00 * 8;pal_G['h0F] <= 'h00 * 8;
        pal_R['h10] <= 'h14 * 8;pal_B['h10] <= 'h14 * 8;pal_G['h10] <= 'h14 * 8;
        pal_R['h11] <= 'h00 * 8;pal_B['h11] <= 'h18 * 8;pal_G['h11] <= 'h0C * 8;
        pal_R['h12] <= 'h00 * 8;pal_B['h12] <= 'h1C * 8;pal_G['h12] <= 'h08 * 8;
        pal_R['h13] <= 'h10 * 8;pal_B['h13] <= 'h1C * 8;pal_G['h13] <= 'h00 * 8;
        pal_R['h14] <= 'h14 * 8;pal_B['h14] <= 'h1C * 8;pal_G['h14] <= 'h00 * 8;
        pal_R['h15] <= 'h1C * 8;pal_B['h15] <= 'h10 * 8;pal_G['h15] <= 'h00 * 8;
        pal_R['h16] <= 'h1C * 8;pal_B['h16] <= 'h00 * 8;pal_G['h16] <= 'h00 * 8;
        pal_R['h17] <= 'h18 * 8;pal_B['h17] <= 'h00 * 8;pal_G['h17] <= 'h0C * 8;
        pal_R['h18] <= 'h10 * 8;pal_B['h18] <= 'h00 * 8;pal_G['h18] <= 'h0C * 8;
        pal_R['h19] <= 'h04 * 8;pal_B['h19] <= 'h00 * 8;pal_G['h19] <= 'h10 * 8;
        pal_R['h1A] <= 'h00 * 8;pal_B['h1A] <= 'h00 * 8;pal_G['h1A] <= 'h10 * 8;
        pal_R['h1B] <= 'h00 * 8;pal_B['h1B] <= 'h0C * 8;pal_G['h1B] <= 'h14 * 8;
        pal_R['h1C] <= 'h00 * 8;pal_B['h1C] <= 'h10 * 8;pal_G['h1C] <= 'h10 * 8;
        pal_R['h1D] <= 'h00 * 8;pal_B['h1D] <= 'h00 * 8;pal_G['h1D] <= 'h00 * 8;
        pal_R['h1E] <= 'h00 * 8;pal_B['h1E] <= 'h00 * 8;pal_G['h1E] <= 'h00 * 8;
        pal_R['h1F] <= 'h00 * 8;pal_B['h1F] <= 'h00 * 8;pal_G['h1F] <= 'h00 * 8;
        pal_R['h20] <= 'h1C * 8;pal_B['h20] <= 'h1C * 8;pal_G['h20] <= 'h1C * 8;
        pal_R['h21] <= 'h0C * 8;pal_B['h21] <= 'h1C * 8;pal_G['h21] <= 'h14 * 8;
        pal_R['h22] <= 'h10 * 8;pal_B['h22] <= 'h1C * 8;pal_G['h22] <= 'h10 * 8;
        pal_R['h23] <= 'h18 * 8;pal_B['h23] <= 'h1C * 8;pal_G['h23] <= 'h0C * 8;
        pal_R['h24] <= 'h1C * 8;pal_B['h24] <= 'h1C * 8;pal_G['h24] <= 'h00 * 8;
        pal_R['h25] <= 'h1C * 8;pal_B['h25] <= 'h1C * 8;pal_G['h25] <= 'h0C * 8;
        pal_R['h26] <= 'h1C * 8;pal_B['h26] <= 'h00 * 8;pal_G['h26] <= 'h10 * 8;
        pal_R['h27] <= 'h1C * 8;pal_B['h27] <= 'h00 * 8;pal_G['h27] <= 'h14 * 8;
        pal_R['h28] <= 'h18 * 8;pal_B['h28] <= 'h00 * 8;pal_G['h28] <= 'h18 * 8;
        pal_R['h29] <= 'h0C * 8;pal_B['h29] <= 'h00 * 8;pal_G['h29] <= 'h18 * 8;
        pal_R['h2A] <= 'h00 * 8;pal_B['h2A] <= 'h00 * 8;pal_G['h2A] <= 'h1C * 8;
        pal_R['h2B] <= 'h08 * 8;pal_B['h2B] <= 'h18 * 8;pal_G['h2B] <= 'h1C * 8;
        pal_R['h2C] <= 'h00 * 8;pal_B['h2C] <= 'h1C * 8;pal_G['h2C] <= 'h1C * 8;
        pal_R['h2D] <= 'h00 * 8;pal_B['h2D] <= 'h00 * 8;pal_G['h2D] <= 'h00 * 8;
        pal_R['h2E] <= 'h00 * 8;pal_B['h2E] <= 'h00 * 8;pal_G['h2E] <= 'h00 * 8;
        pal_R['h2F] <= 'h00 * 8;pal_B['h2F] <= 'h00 * 8;pal_G['h2F] <= 'h00 * 8;
        pal_R['h30] <= 'h1C * 8;pal_B['h30] <= 'h1C * 8;pal_G['h30] <= 'h1C * 8;
        pal_R['h31] <= 'h14 * 8;pal_B['h31] <= 'h1C * 8;pal_G['h31] <= 'h18 * 8;
        pal_R['h32] <= 'h18 * 8;pal_B['h32] <= 'h1C * 8;pal_G['h32] <= 'h14 * 8;
        pal_R['h33] <= 'h1C * 8;pal_B['h33] <= 'h1C * 8;pal_G['h33] <= 'h14 * 8;
        pal_R['h34] <= 'h1C * 8;pal_B['h34] <= 'h1C * 8;pal_G['h34] <= 'h10 * 8;
        pal_R['h35] <= 'h1C * 8;pal_B['h35] <= 'h14 * 8;pal_G['h35] <= 'h14 * 8;
        pal_R['h36] <= 'h1C * 8;pal_B['h36] <= 'h10 * 8;pal_G['h36] <= 'h18 * 8;
        pal_R['h37] <= 'h1C * 8;pal_B['h37] <= 'h08 * 8;pal_G['h37] <= 'h1C * 8;
        pal_R['h38] <= 'h1C * 8;pal_B['h38] <= 'h0C * 8;pal_G['h38] <= 'h1C * 8;
        pal_R['h39] <= 'h14 * 8;pal_B['h39] <= 'h08 * 8;pal_G['h39] <= 'h1C * 8;
        pal_R['h3A] <= 'h10 * 8;pal_B['h3A] <= 'h0C * 8;pal_G['h3A] <= 'h1C * 8;
        pal_R['h3B] <= 'h08 * 8;pal_B['h3B] <= 'h18 * 8;pal_G['h3B] <= 'h1C * 8;
        pal_R['h3C] <= 'h10 * 8;pal_B['h3C] <= 'h1C * 8;pal_G['h3C] <= 'h18 * 8;
        pal_R['h3D] <= 'h00 * 8;pal_B['h3D] <= 'h00 * 8;pal_G['h3D] <= 'h00 * 8;
        pal_R['h3E] <= 'h00 * 8;pal_B['h3E] <= 'h00 * 8;pal_G['h3E] <= 'h00 * 8;
        pal_R['h3F] <= 'h00 * 8;pal_B['h3F] <= 'h00 * 8;pal_G['h3F] <= 'h00 * 8;
    end

    always @(posedge ppu_clk) begin
        $lcd(x,y,R,G,B);
    end

    wire[7:0] rom_dat;
    wire[7:0] ppu_sram_dat;
    wire ppu_sram_cs = ~ppu_addr[13];
    wire ppu_rom_cs = ppu_addr[13];

    //assign ppu_dat = ppu_addr[13]?ppu_sram_cs:ppu_rom_cs;
    //reg[7:0] dat = 'hzz;

    sram ppu_sram(
        ppu_addr[10:0],
        ppu_dat,
        ppu_rw,
        ppu_rd,
        ppu_sram_cs,
        1'b0,
    );

    wire [7:0] rom_out_dat;

    ppu_rom ppu_roma(
        .doa    (ppu_dat),
        .addra  (ppu_addr),
        .clka   (ppu_clk),
        .rsta   (1'b0),
        .cs     (ppu_rom_cs)
    );

    always @(negedge ppu_rd) begin
        dat_out <= rom_dat;
    end

    //wire[8:0] x;
    //wire[8:0] y;

    ppu ppua(
        ppu_addr,
        ppu_dat,
        ppu_clk,
        ppu_rw,
        ppu_rd,

        ppu_cs,
        ppu_nmi,

        //cpu接口
        cpu_addr,
        cpu_dat,
        cpu_rw,

        //输出像素
        out_pixel,
        x,
        y,
        1'b1,
    );

endmodule

module nes_io (
    addr,
    dat,
    rw,
    cs,
);
    input wire cs;
    input wire rw;
    inout wire[7:0] dat;
    input wire[4:0] addr;
    wire #1 cs_delay = cs;
    reg [7:0] io_out = 'd0;

    assign dat = (~cs_delay&rw)?io_out:'hzz;

    always @(negedge cs_delay) begin
        if(rw)begin
            io_out <= 'd0;      //$4000
        end
    end
endmodule

module test_cpu (
);
    wire [15:0] addr;
    wire [7:0] dat;
    wire rw;
    wire rd = 'd0;

    wire ibus = ~addr[15]&clk_in;                               //nes内部总线, $0000~$7fff
    wire ebus = addr[15]&clk_in;                                //外部总线$8000~$ffff
    wire cpu_rom_cs =   ebus;
    wire cpu_sram_cs =  (addr[13]|addr[14])|~ibus;              //
    wire ppu_cs =       (~addr[13]|addr[14])|~ibus;;            //$2000~$2fff

    cpu c65(
        addr,
        dat,
        clk_in,
        1'b1,
        irq,
        ppu_nmi,
        rw
    );

    rom cpu_rom(
        addr[14:0],
        dat,
        rw,
        cpu_rom_cs,
    );

    sram cpu_sram(
        addr[10:0],
        dat,
        rw,
        rd,
        cpu_sram_cs,
        1'b1,
    );

    nes_io io(
        addr[4:0],
        dat,
        rw,
        ~(addr[15:8]=='h40&&clk_in),
    );

    initial begin
        //$dumpfile("wave.vcd");
        //$dumpvars(0,test_cpu);
    end

    reg pixel_clk = 'd0;
    reg clk_in = 'd0;
    reg rst_n = 1'd0;
    wire irq = 1'd1;
    wire nmi = 1'd1;

    //dot时钟是cpu时钟的三倍频
    reg [3:0] clk_ppu_to_cpu = 'd0;
    always @(posedge pixel_clk) begin
        if(clk_ppu_to_cpu=='d2)begin
            clk_ppu_to_cpu <= 'd0;
        end
        else begin
            clk_ppu_to_cpu <= clk_ppu_to_cpu + 4'd1;
        end
        clk_in <= (clk_ppu_to_cpu>='d1);
    end

    always #4       pixel_clk = ~pixel_clk;     //像素时钟
    always #40      rst_n = 'd1;         //等待40个周期时.拉高
    reg dump_init = 'd0;
    reg[23:0] dump_end = 'd0;
    //always #2000000   $finish;
    reg[15:0] frame_cc = 0;
    reg[15:0] frame_cc_end = 'hffff;

    always @(posedge pixel_clk) begin
        if(
            //addr=='h8049&&
        //addr==8715&&
        frame_cc=='h200&&
        //addr=='h8181&&
        ~dump_init)begin
            //写入ppu数据
            $display("1234\n");
            $dumpfile("wave.vcd");
            $dumpvars(0,test_cpu);
            dump_init <= 'b1;
            frame_cc_end <= frame_cc + 'd1;
        end
        // if(addr=='h8049)begin
        //     dump_end = 'd300000;
        // end
        // if(dump_end!='d0) dump_end <= dump_end - 24'd1;
        // if(dump_end=='d1) $finish;
        //if()

        if(x==0&&y==0)begin
            frame_cc <= frame_cc + 16'd1;
            $display("frame:%d,%04X",frame_cc,addr);
            //if(dump_init) $finish;
        end

        if(frame_cc==20)begin   //到第几帧就关闭
        //if(x==0&&y==2)begin     //第几个像素关闭
            $finish;
        end
    end

    wire[13:0] ppu_addr;
    wire[7:0]  ppu_dat;
    wire ppu_clk = pixel_clk;
    wire ppu_rw;
    wire ppu_rd;
    wire ppu_nmi;

    wire[2:0] cpu_addr = addr[2:0];
    //wire[7:0] cpu_dat = dat;
    wire cpu_rw = rw;
    wire [8:0] out_x,out_y;
    wire[5:0] out_pixel;
    wire[8:0] x;
    wire[8:0] y;

    ppu_test2 ptest(
        ppu_addr,
        ppu_dat,
        ppu_rw,
        ppu_rd,
        ppu_clk,
        ppu_nmi,
        dat_out,
        out_pixel,

        cpu_addr,
        dat,
        cpu_rw,
        ppu_cs,

        x,
        y,
    );

endmodule
